/*
1. 传递临时对象作为线程参数
 要避免的陷阱:

 为了标记线程，使之明白自己被分配的任务，需要给线程传递参数

//引用参数i，在主线程传入myvar的时候，i的地址和myvar的地址不相同
//（本应该是相同的，因为是引用），说明并不是真正的myvar的引用，而是把myvar的值
//复制了一份，所以，用引入传入是安全的，但是不建议这样使用

 引用传递是安全的，但是指针传递是不安全的
 绝对不可以使用指针传参（深拷贝和浅拷贝的问题？)

 解决方法：（防止内存的非法引用）
 对于int等类型，我们可以直接传值，防止在不同的编译器产生不同的效果
 对于字符串类型或类对象，要避免隐式类型转换，全部在创建线程这一行就构建出临时对象来
在函数形参里面要用引用来接，否则还会构造一次临时对象（一共创建了3个对象，很浪费）


 2.线程id的概念
 和进程的Pid一样，每个线程也有对应的id即tid
在线程中，线程ID的类型是pthread_t类型，由于在Linux下线程采用POSIX标准，
所以，在不同的系统下，pthread_t的类型是不同的

线程id可以用cpp的标准库里的函数获取。
std：：this_thread::get_id();

 3.临时对象构造时机抓捕
 创建类A
 通过a的构造函数的调用得知，在thread类中使用临时对象，会在主线程中构造对象
 避免了主线程过早结束而对象无法创建的情况

 4.传递类对象，智能指针做为线程参数
	因为在线程函数中的类对象形参会在构造对象时，thread内部拷贝了一个新的对象
	所以子线程中的对象无法反映到线程外部

	因为使用mutable关键字对类对象进行修改很不方便，而且我们明明传入的是引用，却按照
	值的方式显示、处理，内部的修改不能反应到外部，不符合我们的预期。

	解决：std：：ref（）标准库函数
	明确的表明我们需要传引用（地址），也不再进行拷贝构造 

智能指针：使用move传参，使用join保证内存不泄露

 5.用成员函数指针做线程函数


*/




#include<iostream>
#include<mutex>
#include<string	>
#include<thread>
using namespace std;


//引用参数i，在主线程传入myvar的时候，i的地址和myvar的地址不相同
//（本应该是相同的，因为是引用），说明并不是真正的myvar的引用，而是把myvar的值
//复制了一份，所以，用引入传入是安全的，但是不建议这样使用
//第三个字符串，因为相当于指针，所以会地址相同，出现问题
//对于字符数组，我们可以更改为实参为char*，用const string &形参接受，
//此时出现隐式类型转换，形参地址和实参地址变为不同
// 
//问题部分解决，但是这个隐式类型转换是什么时候转换？
// 在实际情况中，可能在主线程执行完并销毁字符数组后，才进行隐式类型转换
// 这导致string接受的内容不确定和错误
//void myprint(const int& i, char* pmybuf)

//创建一个类来验证临时变量的创建时间
class A {
public:
	//此关键字代表这个变量在任何时候都可以被修改（const也不例外）
	mutable int m_i;

	int m_i;
	//类型转换构造函数，只有一个参数并且是int型，可以把可以int转换为
	//A类对象
	A(int a) :m_i(a) { cout << "a的构造函数" << this<<"threadId is:"<<std::this_thread::get_id()<<endl; }
	A(const A& a):m_i(a.m_i) { cout << "a的拷贝构造" <<this<<"threadId is:"<<std::this_thread::get_id()<< endl; }
	~A() { cout << "a的析构" << this<<"threadId is:"<<std::this_thread::get_id()<<endl; }

	//线程入口
	void thread_work(int num){
		cout<<"子线程thread-work执行"<<endl;
	}

	//类对象作为入口函数
	//仿函数，使类变为可调用对象
	void operator()(int num){
		cout<<"子线程operator执行"<<this_thread.get_id()<<endl;
	}
};

//void myprint(const int& i, const string &pmybuf) {
//	cout << i << endl;
//	
//	//cout<<pmybuf<<endl;
//	cout << pmybuf.c_str() << endl;
//
//	return;
//}

//线程，测试class A
void myprint(const int& i, const A& pmybuf) {
	//pmybuf的对象地址
	cout << &pmybuf << endl;
	return;
}

//线程2测试线程id
//加了mutable的变量可以被修改，即便有const
//void myprint2(const A& pmybuf)

//智能指针形式
void myprint2(unique_ptr<int> pzn){
	//修改该值，不会影响到main函数
	pmybuf.m_i=199;
	cout<<"myprint2 的参数地址："<<&pmybuf<<"threadID "<<std::this_thread::get_id()<<endl;
}


int main(int argc, char* argv[]) {

	//int mvar = 1;
	//int& mvary = mvar;

	////对于这个字符串数组，我们是希望在传参的时候，复制一份给子线程，防止
	////detach状态下，主线程销毁字符数组后我们的子线程出现问题
	//char mybuf[] = "this is a test!";
	////第一个参数是函数名，之后的参数为函数的参数
	////thread mytobj(myprint, mvar, mybuf);
	////最终传入形式
	////这种写法可以完全保证子线程中的字符串对象有效并且不会被回收
	////最稳定的
	//thread mytobj(myprint, mvar, string(mybuf));
	//
	////mytobj.join();

	////主线程和子线程分别执行，可能主线程先执行完，
	////此时，主线程内定义的变量mvar会被释放，而我们用引用的方式把mvar传递到了
	////子线程
	//mytobj.detach();

	int myvar = 1;
	int mysecondpar = 12;

	//希望整形转换成a类对象，传递给第二个参数
	//thread mytobj(myprint, myvar, mysecondpar);

	//利用临时对象
	//必然看到构造函数的执行，甚至还有拷贝
	//再类中加上this，查看当前对象的地址，最后运行结果得知
	//程序首先创建了一个临时对象，并且复制出了另一个对象，并用复制出的对象传入函数
	//此时的程序是安全的
	//另外，即便在函数形参写的是引用传入，实际上还是复制了一个对象进行传入（传值）
	//这与thread内部有关
	//thread mytobj(myprint, myvar, A(mysecondpar));
	//mytobj.join();//join可以等待子线程完全执行完毕
	//主线程过早结束导致主线程无法完成构造函数的执行
	//说明构造函数是在main结束后在执行




	// cout<<"主线程id是"<<this_thread::get_id()<<endl;
	// int myvar=1;
	// //结果表明下行代码构造函数是在子线程中调用的
	// thread mythread2(myprint2,mvar);
	// //下行代码是在主线程创建对象的
	// thread mythread3(myprint2,A(mvar));
	// mythread2.join();

	// //初始化为10
	// A myobj(10);
	// //将类对象作为线程参数
	// //加上ref后，是真正的传引用
	// thread mytobj(myprint2,ref(myobj));
	// //在线程函数中对myobj进行修改
	// //但是结果仍为10
	// myobj.join();


	//使用智能指针
	//unique_ptr<int> myp(new int(100));
	//不能直接作为参数传递
	//thread mytobj(myprint2,myp);
	//使用move
	//这样，形参的智能指针就接管了main中的myp
	//这是正确的，但是会在detach状态下出现问题
	//因为这样，在main执行完后会释放原来的内存，但是子线程的指针正指向这一片内存
	//thread mytobj(myprint2,move(myp))



	//5.用成员函数指针做线程函数
	//创建对象
	A myobj(10);
	//函数名，对象，函数参数
	//thread mytobj(&A::thread,myobj,15);
	//join和detach没有问题，第二个参数也被内部拷贝了
	//强制引用传参,这样就不会拷贝了
	//可以使用&但是没有效果
	//此时不能用detach
	//thread mytobj(&A::thread,ref(myobj),15); 
	

	//也会调用拷贝构造
	//thread mytobj(myobj,15);
	//强制引用调用
	//不可以使用&，会报错
	//thread mytobj(ref(myobj),15);




	mytobj.join();



	//mytobj.detach();











	cout << "I love china" << endl;
	return 0;

}
